using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

namespace DotNetX
{
    /// <summary>
    /// This class allows a key-value pair based string to be accessed indexable
    /// For instance, the string "myKey1=value1&myKey2=value2&myKey=value3" can be accessed like this:
    /// indexableString["value2"]
    /// The values in the string must be of the same type as the templated type
    /// </summary>
    public class IndexableString <TValue>
    {
        /// <summary>
        /// The character that seperates the key-value pairs
        /// </summary>
        public char KeyValueSeperator
        {
            get
            {
                // return the value
                return this._keyValueSeperator;
            }
            set
            {
                // set the value
                this._keyValueSeperator = value;
            }
        }
        private char _keyValueSeperator = '&';

        /// <summary>
        /// The character that is used to value to key assignment
        /// </summary>
        public char AssignmentCharacter
        {
            get
            {
                // return the value
                return this._assignmentCharacter;
            }
            set
            {
                // set the value
                this._assignmentCharacter = value;
            }
        }
        private char _assignmentCharacter = '=';

        /// <summary>
        /// The string that we are indexing
        /// </summary>
        public string IndexedString
        {
            get
            {
                // return the string
                return this._indexedString;
            }
            set
            {
                // set the string
                this._indexedString = value;

                // build the index
                this.BuildIndex();
            }
        }
        private string _indexedString = string.Empty;

        /// <summary>
        /// This returns the number of indexes in the string
        /// </summary>
        public int Count
        {
            get
            {
                // return the index count
                return this._keyValues.Count;
            }
        }

        /// <summary>
        /// The indexed string values
        /// </summary>
        private IDictionary<string, TValue> _keyValues = new Dictionary<string, TValue>();

        /// <summary>
        /// Default constructor
        /// </summary>
        public IndexableString()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="indexableString">The string to index</param>
        public IndexableString(string indexableString)
        {
            if (indexableString == null)
                throw new ArgumentNullException("'indexableString' is null.");

            // set the string
            this.IndexedString = indexableString;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="keyValueSeperator">The character that is used to value to key assignment</param>
        /// <param name="assignmentCharacter">The string that we are indexing</param>
        public IndexableString(char keyValueSeperator, char assignmentCharacter)
        {
            // set the characters
            this.KeyValueSeperator = keyValueSeperator;
            this.AssignmentCharacter = assignmentCharacter;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="indexableString">The string to index</param>
        /// <param name="keyValueSeperator">The character that is used to value to key assignment</param>
        /// <param name="assignmentCharacter">The string that we are indexing</param>
        public IndexableString(string indexableString, char keyValueSeperator, char assignmentCharacter)
        {
            if (indexableString == null)
                throw new ArgumentNullException("'indexableString' is null.");

            // set the values
            this.KeyValueSeperator = keyValueSeperator;
            this.AssignmentCharacter = assignmentCharacter;
            this.IndexedString = indexableString;
        }

        /// <summary>
        /// This returns the value of the desired index.
        /// If the passed index does not exist, null or 0 is returned - depending on whether the type is Nullable or if it is a value-type
        /// </summary>
        /// <param name="index">The index to return the value to</param>
        /// <returns>The values belonging to the desired index</returns>
        public TValue this[string index]
        {
            get
            {
                if (index == null)
                    throw new ArgumentNullException("'index' is null.");

                // does the index exist?
                if (!this._keyValues.ContainsKey(index))
                    return this.ReturnNullForValue();

                // return the value
                return this._keyValues[index];
            }
        }

        /// <summary>
        /// This returns a null value for the template type
        /// This will return 0 if the type is a value type (eg, int, double, char etc...)
        /// This will return null if the type is a Nullable type
        /// </summary>
        /// <returns></returns>
        private TValue ReturnNullForValue()
        {
            // get the type of the template value
            Type type = typeof(TValue);

            // is the type a class or an interface?
            if (type.IsClass || type.IsInterface)
                return (TValue)(object)null;

            // the type must be a form of value type (int, double, char, Enum - is int based - etc...)
            return (TValue)Convert.ChangeType((object)0, typeof(TValue), CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// This builds the string index
        /// </summary>
        private void BuildIndex()
        {
            // do we have a string?
            if (string.IsNullOrEmpty(this.IndexedString))
                return;

            // clear the old values
            this._keyValues.Clear();

            // split the string
            string[] keys = this.IndexedString.Split(this.KeyValueSeperator);

            // do we have any keys?
            if (keys.Length == 0)
                return;

            // add each key
            foreach (string key in keys)
            {
                // add they key
                this.AddKeyToIndex(key.Trim());
            }
        }

        /// <summary>
        /// This adds the passed key to the index
        /// </summary>
        /// <param name="key"></param>
        private void AddKeyToIndex(string key)
        {
            if (key == null)
                throw new ArgumentNullException("'key' is null.");

            // do we have a key?
            if (key.Length == 0)
                return;

            // split the key
            string[] values = key.Split(this.AssignmentCharacter);

            // a valid key?
            if (values.Length == 0)
                return;

            // add the values
            KeyValuePair<string, TValue> pair = new KeyValuePair<string, TValue>(values[0], (TValue)Convert.ChangeType(values[1], typeof(TValue), CultureInfo.CurrentCulture));
            this._keyValues.Add(pair);
        }
    }
}
